package fr.bludwarf.skywarrior.joueur;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Queue;
import java.util.Set;

import fr.bludwarf.skywarrior.evenements.Evenement;
import fr.bludwarf.skywarrior.evenements.EvenementImpossible;
import fr.bludwarf.skywarrior.evenements.TerminerRecherche;
import fr.bludwarf.skywarrior.exceptions.ConstructionImpossibleException;
import fr.bludwarf.skywarrior.exceptions.EtatIncoherentException;
import fr.bludwarf.skywarrior.exceptions.EvenementInconnuOublieException;
import fr.bludwarf.skywarrior.exceptions.RessourcesInsuffisantesException;
import fr.bludwarf.skywarrior.global.EtatMonde;
import fr.bludwarf.skywarrior.global.EtatPere;
import fr.bludwarf.skywarrior.global.Monde;
import fr.bludwarf.skywarrior.global.Temps;
import fr.bludwarf.skywarrior.global.TimeFactorInconnu;
import fr.bludwarf.skywarrior.ile.Batiment;
import fr.bludwarf.skywarrior.ile.Construction;
import fr.bludwarf.skywarrior.ile.EtatIle;
import fr.bludwarf.skywarrior.ile.Ile;
import fr.bludwarf.skywarrior.ile.Score;

/**
 * Etat d'un joueur
 * @since 2 nov. 2011
 */
public class EtatJoueur extends EtatPere<Joueur, Ile, EtatIle> {
	
	public static final Ressources STOCKS_INIT = new Ressources(100, 30, 0);

	/** Stocks */
	// TODO : prendre en compte la population
	Ressources _stocks = STOCKS_INIT;

	/** Nombre de vaisseaux de chaque type */
	HashMap<Vaisseau, Integer> _vaisseaux = new HashMap<Vaisseau, Integer>();

	/** Technos connues */
	List<Techno> _technos = new ArrayList<Techno>();

	/** Technos en recherche */
	Queue<TerminerRecherche> _recherches = new ArrayDeque<TerminerRecherche>();

	private Score _score = new Score();
	
	public EtatJoueur(final Joueur joueur, final EtatMonde etatPere) {
		super(joueur, etatPere);
		super.init();
		
		// Lien joueur et monde
		if (!etatPere.getMonde().contient(joueur))
			throw new EtatIncoherentException(String.format(
				"Le joueur %s doit exister dans ce monde avant de pouvoir créer un nouvel état pour ce joueur.",
				joueur.getNom()));
		
		// De base on a au moins une ile avec un palais
		final Ile ilePrincipale = joueur.getIlePrincipale();
		if (ilePrincipale != null)
		{
			add(ilePrincipale);
		}
	}
	
	public EtatJoueur(final EtatJoueur etat) throws Exception {
		super(etat);
		_stocks = (Ressources) etat._stocks.clone();
		_vaisseaux = (HashMap<Vaisseau, Integer>) etat._vaisseaux.clone();
	}

	public EtatJoueur(EtatJoueur etatInitial,
			EtatPere<Monde, Joueur, EtatJoueur> etatPere) throws Exception
	{
		this(etatInitial);
		_etatPere = etatPere;
	}

	/**
	 * @return la production totale de matériaux, d'éther et de nourriture du joueur
	 */
	public Ressources getProduction(final boolean cumulNourriture)
	{
		Ressources prod = new Ressources(0, 0, 0); // FIXME : on commence avec combien de pop
		
		for (final EtatIle etatIle : getEtatsIles().values())
		{
			prod.add(etatIle.getProduction(cumulNourriture), cumulNourriture);
		}
		
		return prod;
	}
	
	/**
	 * Ajoute un nouveau bâtiment dans la liste des bâtiments à construire pour une ile donnée
	 * @param batiment bâtiment à construire
	 * @param ile ile sur laquelle construire le bâtiment
	 * @throws TimeFactorInconnu si le TimeFactor est inconnu pour le type demandé
	 */
	public void construire(final Batiment batiment, final int ile) throws TimeFactorInconnu
	{
		final int num = checkIle(ile);
		getEtatsIles().get(num - 1).construire(batiment);
	}

	/**
	 * Ajoute un nouveau bâtiment dans la liste des bâtiments à construire sur <b>l'ile principale</b> du joueur
	 * @param batiment bâtiment à construire
	 * @throws TimeFactorInconnu si le TimeFactor est inconnu pour le type demandé
	 */
	public void construire(final Batiment batiment) throws TimeFactorInconnu
	{
		construire(batiment, 1);
	}
	
	private int checkIle(int ile) {
		// Ile connue ?
		if (ile >= 1 && ile <= getEtatsIles().size())
		{
			return ile;
		}
		else
		{
			System.err.println("L'ile numéro " + ile + " n'existe pas, on utilise l'ile principale");
			return 1;
		}
	}
	
	/**
	 * @param ile ile dont souhaite obtenir la liste des construction en cours
	 * @return liste des construction en cours de l'ile
	 */
	public Queue<Construction> constructionsEnCours(final int ile)
	{
		final int num = checkIle(ile);
		return getEtatsIles().get(num - 1).getConstructions();
	}

	/**
	 * Ajoute une certaine quantité de matériaux et d'éther. La production de nourriture ne se cumule pas.
	 * @param production quantité de ressource à ajouter au joueur
	 */
	public void ajouter(final Ressources production) {
		_stocks.add(production, false);
	}
	
	@Override
	public String toString() {
		final StringBuilder sb = new StringBuilder();
		final Ressources stocks = getStocks();
		final Ressources prod = getProduction(false);
		
		for (final EtatIle etat : getEtatsIles().values())
		{
			sb.append(String.format("    %-12s(m=%04d+%02d,e=%04d+%02d,u=%02d/%02d) : %s",
				_entite.getNom(),
				stocks.materiaux(),
				prod.materiaux(),
				stocks.ether(),
				prod.ether(),
				getPop(), // unités TODO
				getPopMax(), // unités max TODO,
				etat
			));
		}

		return sb.toString();
	}

	public HashMap<Ile, EtatIle> getEtatsIles() {
		
		return getEtatsFils();
	}

	public EtatIle getEtatIle(Ile ile) {
		return getEtatsIles().get(ile);
	}

	@Override
	// TODO : prochainEvenement
	public Evenement prochainEvenement() throws Exception {
		return Evenement.prochainEvenementParmis(
				// prochaine techno
				// prochaine conquête d'ile
				Evenement.prochainEvenementDe(getEtatsIles().values()) // prochain évènement parmis toutes les iles
		);
	}

	@Override
	protected EtatIle etatInitialFils(Ile fils) {
		return new EtatIle(fils, this);
	}

	public void setStocks(int materiaux, int ether) {
		_stocks.materiaux(materiaux);
		_stocks.ether(ether);
	}
	
	public Ressources getStocks()
	{
		return _stocks;
	}
	
	public int getPop()
	{
		int pop = 0;
		
		for (final Vaisseau vaisseau : _vaisseaux.keySet())
		{
			final Integer nb = _vaisseaux.get(vaisseau);
			pop += nb * vaisseau.getPop();
		}
		
		return pop;
	}
	
	public int getPopMax()
	{
		int max = 20;
		
		final HashMap<Ile, EtatIle> etatsIles = getEtatsIles();
		for (final Ile ile : etatsIles.keySet())
		{
			final EtatIle etatIle = etatsIles.get(ile);
			max += etatIle.getPopMax();
		}
		
		return max;
	}
	
	/**
	 * @return true si on peut continuer ou commencer les constructions sur au moins une ile
	 * @throws Exception 
	 * @since 10 nov. 2011
	 */
	public boolean peutConstruire() throws Exception
	{
		final HashMap<Ile, EtatIle> etatsIles = getEtatsIles();
		for (final Ile ile : etatsIles.keySet())
		{
			final EtatIle etatIle = etatsIles.get(ile);
			if (etatIle.peutConstruire())
			{
				return true;
			}
		}
		
		return false;
	}

	public void consommer(Ressources cout) throws RessourcesInsuffisantesException {
		_stocks.remove(cout);
	}

	/**
	 * Prise en compte uniquement des matériaux et de l'éther
	 * @param ress
	 * @return
	 * @since 14 nov. 2011
	 */
	public double tempsPourCollecter(Ressources cout) {
		// Production
		final Ressources prod = getProduction(false);
		
		// Temps max pour collecter les matériaux
		double tMat = getTemps().tempsPourCollecter(cout.materiaux(), prod.materiaux(), _stocks.materiaux());
		if (tMat > 0)
		{
			System.out.println("On doit attendre " + Temps.toString(tMat) + " pour collecter " + cout.materiaux() + " matériaux");
		}
		
		// Temps max pour collecter l'éther
		double tEth = getTemps().tempsPourCollecter(cout.ether(), prod.ether(), _stocks.ether());
		if (tEth > 0)
		{
			System.out.println("On doit attendre " + Temps.toString(tEth) + " pour collecter " + cout.ether() + " d'éther");
		}
		
		return Math.max(tMat, tEth);
	}

	public void add(Vaisseau vaisseau) {
		if (!_vaisseaux.containsKey(vaisseau))
		{
			_vaisseaux.put(vaisseau, 1);
		}
		else
		{
			_vaisseaux.put(vaisseau, _vaisseaux.get(vaisseau) + 1);
		}
	}
	
	/**
	 * Ajout de points dans le score du joueur
	 * @param nb points gagnés
	 */
	public Score getScore()
	{
		return _score;
	}

	public boolean aPrevuDeConstruire(Unite unite)
	{		
		final HashMap<Ile, EtatIle> etatsIles = getEtatsIles();
		for (Ile ile : etatsIles.keySet())
		{
			final EtatIle etatIle = etatsIles.get(ile);
			if (etatIle.constructionPrevue(unite))
			{
				return true;
			}
		}
		return false;
	}

	@Override
	protected EtatIle newEtatFils(EtatIle etatInitial,
			EtatPere<Joueur, Ile, EtatIle> etatPere) throws Exception
	{
		return new EtatIle(etatInitial, this);
	}

	public void peutConstruire(Unite unite)
	{
		// TODO Auto-generated method stub
		
	}

	public Joueur getJoueur()
	{
		return _entite;
	}

	public int getNb(Vaisseau vaisseau)
	{
		if (!_vaisseaux.containsKey(vaisseau))
			return 0;
		else
			return _vaisseaux.get(vaisseau);
	}

	public int getNbVaisseaux()
	{
		int nb = 0;
		
		for (final Vaisseau v : _vaisseaux.keySet())
		{
			nb += _vaisseaux.get(v);
		}
		
		return nb;
	}

	public boolean connait(final EffetsSurTempsDeRecherche techno)
	{
		return _technos.contains(techno);
	}

	public void add(int nb, Vaisseau vaisseau)
	{
		for (int i = 0; i < nb; ++i)
			add(vaisseau);
	}

	/**
	 * @return technos connues
	 */
	public List<Techno> getTechnos()
	{
		return _technos;
	}

	public void add(Techno techno)
	{
		_technos.add(techno);
	}

	public void add(TerminerRecherche rch)
	{
		_recherches.add(rch);
	}

	public TerminerRecherche getRechercheEnCours()
	{
		return _recherches.peek();
	}

	public Set<Vaisseau> getVaisseaux()
	{
		return _vaisseaux.keySet();
	}

	public Queue<TerminerRecherche> getRecherches()
	{
		return _recherches;
	}

	public EtatMonde getEtatMonde()
	{
		return (EtatMonde) getEtatPere();
	}

	public Ressources getProduction()
	{
		final Ressources prod = new Ressources(0, 0, 0);
		
		final HashMap<Ile, EtatIle> etatsIles = getEtatsIles();
		for (final Ile ile : etatsIles.keySet())
		{
			final EtatIle etatIle = etatsIles.get(ile);
			prod.add(etatIle.getProduction(false), false);
		}
		
		return prod;
	}
	
	@Override
	public void avancer(double temps) throws Exception
	{
		// TODO avancer les recherches
		super.avancer(temps);
	}

	public Set<Batiment> getBatiments()
	{
		final Set<Batiment> batiments = new LinkedHashSet<Batiment>();
		
		final HashMap<Ile, EtatIle> etatsIles = getEtatsIles();
		for (Ile ile : etatsIles.keySet())
		{
			final EtatIle etatIle = etatsIles.get(ile);
			batiments.addAll(etatIle.getBatiments());
		}
		
		return batiments;
	}

	@Override
	public void execute(Evenement e)
	{
		// à implémenter pour le type d'évènement précis
	}
}
